﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace Develore.GDiff
{
	internal class StreamSeeker
	{
		public StreamSeeker(Stream source, int blockSize)
		{
			if (null == source)
				throw new ArgumentNullException("source");
			
			this.SourceStream = source;

			byte[] buf = new byte[Math.Min(source.Length, blockSize)];
			source.Read(buf, 0, buf.Length);

			List<byte> bytes = new List<byte>(buf);

			using (Adler32 adler = new Adler32(buf))
			{
				string key = BitConverter.ToString(adler.Hash).Replace("-", "");
				this.AddBlockPointer(key, source.Position - buf.Length, buf.Length);

				while (source.Position < source.Length - 1)
				{
					bytes.Add((byte)source.ReadByte());
					adler.ComputeNext(bytes.First(), bytes.Last());
					bytes.RemoveAt(0);

					key = BitConverter.ToString(adler.Hash).Replace("-", "");
					this.AddBlockPointer(key, source.Position - buf.Length, buf.Length);
				}
			}
		}

		private Stream SourceStream = null;
		private Dictionary<string, List<BlockPointer>> Hashes = new Dictionary<string, List<BlockPointer>>();
		private Dictionary<long, string> StrongHashes = new Dictionary<long, string>();


		public long? FindBlockPosition(DiffBlock block)
		{
			if (this.Hashes.ContainsKey(block.WeakHashString))
			{
				foreach (BlockPointer pointer in this.Hashes[block.WeakHashString])
				{
					if (this.StrongHashes.ContainsKey(pointer.Index))
					{
						if (this.StrongHashes[pointer.Index] == block.StrongHashString)
						{
							return pointer.Index;
						}
					}
					else
					{
						this.SourceStream.Position = pointer.Index;
						byte[] buffer = new byte[pointer.Length];
						this.SourceStream.Read(buffer, 0, buffer.Length);

						using (MD5 md5 = MD5.Create())
						{
							md5.ComputeHash(buffer);
							string hash = BitConverter.ToString(md5.Hash).Replace("-", "");
							this.StrongHashes.Add(pointer.Index, hash);

							if (hash == block.StrongHashString)
								return pointer.Index;
						}
					}
				}
			}

			return null;
		}

		private void AddBlockPointer(string key, long position, int length)
		{
			if (!this.Hashes.ContainsKey(key))
				this.Hashes.Add(key, new List<BlockPointer>());

			this.Hashes[key].Add(new BlockPointer(position, length));
		}

		
	}
}
